use std::{
    env, fs,
    fs::File,
    io,
    io::Write,
    path::{Path, PathBuf},
    process::Command,
};

use cargo_lock::{Lockfile, package::SourceKind};

fn get_vector_lock_path() -> PathBuf {
    let path = fs::canonicalize(env::var("CARGO_MANIFEST_DIR").unwrap()).unwrap();

    // Remove the "lib/vector-vrl/web-playground" suffix
    let parent_path = path
        .parent()
        .and_then(|p| p.parent())
        .and_then(|p| p.parent());
    parent_path
        .expect("Failed to find vector repo root")
        .join("Cargo.lock")
        .to_path_buf()
}

fn get_git_hash() -> String {
    let output = Command::new("git")
        .args(["rev-parse", "HEAD"])
        .output()
        .expect("Failed to get git HEAD sha");
    String::from_utf8(output.stdout).unwrap().trim().to_string()
}

fn create_const_statement(name: &str, value: &str) -> String {
    format!("pub const {name}: &str = \"{value}\";\n")
}

fn write_vector_constants(output_file: &mut File) {
    // TODO: For releases, we should use the manifest.package().version().
    // https://github.com/vectordotdev/vector/issues/18425
    let vector_git_sha = get_git_hash();
    let vector_version_statement = create_const_statement("VECTOR_VERSION", &vector_git_sha);
    output_file
        .write_all(vector_version_statement.as_bytes())
        .expect("Failed to write Vector version constant");

    let vector_link = format!("https://github.com/vectordotdev/vector/tree/{vector_git_sha}");
    let vector_link_statement = create_const_statement("VECTOR_LINK", &vector_link);
    output_file
        .write_all(vector_link_statement.as_bytes())
        .expect("Failed to write Vector version constant");
}

fn write_vrl_constants(lockfile: &Lockfile, output_file: &mut File) {
    let vrl_dep = lockfile
        .packages
        .iter()
        .find(|&package| package.name.as_str() == "vrl")
        .expect("missing VRL dependency");

    let (version, link) = if let Some(vrl_source) = vrl_dep.source.clone() {
        match vrl_source.kind() {
            SourceKind::Git(_) => {
                let precise = vrl_source
                    .precise()
                    .expect("git reference should have precise")
                    .to_string();

                let mut url = vrl_source.url().to_string();
                url = url
                    .trim_end_matches(".git")
                    .trim_end_matches('/')
                    .to_string();

                (precise.clone(), Some(format!("{url}/tree/{precise}")))
            }
            SourceKind::Registry if vrl_source.is_default_registry() => {
                let version = vrl_dep.version.to_string();
                (
                    version.clone(),
                    Some(format!("https://crates.io/crates/vrl/{version}")),
                )
            }
            SourceKind::Path => (vrl_dep.version.to_string(), None),
            kind => unimplemented!("unhandled source kind: {:?}", kind),
        }
    } else {
        (vrl_dep.version.to_string(), None)
    };

    output_file
        .write_all(create_const_statement("VRL_VERSION", &version).as_bytes())
        .expect("Failed to write VRL version constant");

    let link_str = link.as_deref().unwrap_or("");
    output_file
        .write_all(create_const_statement("VRL_LINK", link_str).as_bytes())
        .expect("Failed to write VRL_LINK constant");
}

fn write_build_constants(lockfile: &Lockfile, dest_path: &Path) -> io::Result<()> {
    let mut output_file = File::create(dest_path)?;
    output_file.write_all(
        "// AUTOGENERATED CONSTANTS. SEE BUILD.RS AT REPOSITORY ROOT. DO NOT MODIFY.\n".as_ref(),
    )?;
    write_vector_constants(&mut output_file);
    write_vrl_constants(lockfile, &mut output_file);
    Ok(())
}

fn main() {
    let lockfile =
        Lockfile::load(get_vector_lock_path()).expect("Failed to load Vector Cargo.lock");
    let dst = Path::new(&env::var("OUT_DIR").unwrap()).join("built.rs");
    write_build_constants(&lockfile, &dst).expect("Failed to write constants");
}
